<!DOCTYPE html>
<!--
Copyright (c) 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/scalar.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/base/utils.html">

<script>
'use strict';

/**
 * @fileoverview Provides the MemoryAllocatorDump class.
 */
tr.exportTo('tr.model', function() {
  /**
   * @constructor
   */
  function MemoryAllocatorDump(containerMemoryDump, fullName, opt_guid) {
    this.fullName = fullName;
    this.parent = undefined;
    this.children = [];

    // String -> Scalar.
    this.numerics = {};

    // String -> string.
    this.diagnostics = {};

    // The associated container memory dump.
    this.containerMemoryDump = containerMemoryDump;

    // Ownership relationship between memory allocator dumps.
    this.owns = undefined;
    this.ownedBy = [];

    // Map from sibling dumps (other children of this dump's parent) to the
    // proportion of this dump's size which they (or their descendants) own.
    this.ownedBySiblingSizes = new Map();

    // Retention relationship between memory allocator dumps.
    this.retains = [];
    this.retainedBy = [];

    // Weak memory allocator dumps are removed from the model after import in
    // tr.model.GlobalMemoryDump.removeWeakDumps(). See
    // base::trace_event::MemoryAllocatorDump::Flags::WEAK in the Chromium
    // codebase.
    this.weak = false;

    // A list of information about the memory allocator dump (e.g. about how
    // its fields were calculated). Each item should be an object with
    // a mandatory 'type' property and type-specific extra arguments (see
    // MemoryAllocatorDumpInfoType).
    this.infos = [];

    // For debugging purposes.
    this.guid = opt_guid;
  }

  /**
   * Size numeric names. Please refer to the Memory Dump Graph Metric
   * Calculation design document for more details (https://goo.gl/fKg0dt).
   */
  MemoryAllocatorDump.SIZE_NUMERIC_NAME = 'size';
  MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME = 'effective_size';
  MemoryAllocatorDump.RESIDENT_SIZE_NUMERIC_NAME = 'resident_size';
  MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME =
      MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME;

  MemoryAllocatorDump.prototype = {
    get name() {
      return this.fullName.substring(this.fullName.lastIndexOf('/') + 1);
    },

    get quantifiedName() {
      return '\'' + this.fullName + '\' in ' +
          this.containerMemoryDump.containerName;
    },

    getDescendantDumpByFullName(fullName) {
      return this.containerMemoryDump.getMemoryAllocatorDumpByFullName(
          this.fullName + '/' + fullName);
    },

    isDescendantOf(otherDump) {
      if (this === otherDump) return true;
      if (this.parent === undefined) return false;
      return this.parent.isDescendantOf(otherDump);
    },

    addNumeric(name, numeric) {
      if (!(numeric instanceof tr.b.Scalar)) {
        throw new Error('Numeric value must be an instance of Scalar.');
      }
      if (name in this.numerics) {
        throw new Error('Duplicate numeric name: ' + name + '.');
      }
      this.numerics[name] = numeric;
    },

    addDiagnostic(name, text) {
      if (typeof text !== 'string') {
        throw new Error('Diagnostic text must be a string.');
      }
      if (name in this.diagnostics) {
        throw new Error('Duplicate diagnostic name: ' + name + '.');
      }
      this.diagnostics[name] = text;
    },

    aggregateNumericsRecursively(opt_model) {
      const numericNames = new Set();

      // Aggregate descendants's numerics recursively and gather children's
      // numeric names.
      this.children.forEach(function(child) {
        child.aggregateNumericsRecursively(opt_model);
        for (const [item, value] of Object.entries(child.numerics)) {
          numericNames.add(item, value);
        }
      }, this);

      // Aggregate children's numerics.
      numericNames.forEach(function(numericName) {
        if (numericName === MemoryAllocatorDump.SIZE_NUMERIC_NAME ||
            numericName === MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME ||
            this.numerics[numericName] !== undefined) {
          // Don't aggregate size and effective size numerics. These are
          // calculated in GlobalMemoryDump.prototype.calculateSizes() and
          // GlobalMemoryDump.prototype.calculateEffectiveSizes respectively.
          // Also don't aggregate numerics that the parent already has.
          return;
        }

        this.numerics[numericName] = MemoryAllocatorDump.aggregateNumerics(
            this.children.map(function(child) {
              return child.numerics[numericName];
            }), opt_model);
      }, this);
    }
  };

  // TODO(petrcermak): Consider moving this to tr.v.Histogram.
  MemoryAllocatorDump.aggregateNumerics = function(numerics, opt_model) {
    let shouldLogWarning = !!opt_model;
    let aggregatedUnit = undefined;
    let aggregatedValue = 0;

    // Aggregate the units and sum up the values of the numerics.
    numerics.forEach(function(numeric) {
      if (numeric === undefined) return;

      const unit = numeric.unit;
      if (aggregatedUnit === undefined) {
        aggregatedUnit = unit;
      } else if (aggregatedUnit !== unit) {
        if (shouldLogWarning) {
          opt_model.importWarning({
            type: 'numeric_parse_error',
            message: 'Multiple units provided for numeric: \'' +
                aggregatedUnit.unitName + '\' and \'' + unit.unitName + '\'.'
          });
          shouldLogWarning = false;  // Don't log multiple warnings.
        }
        // Use the most generic unit when the numerics don't agree (best
        // effort).
        aggregatedUnit = tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
      }

      aggregatedValue += numeric.value;
    }, this);

    if (aggregatedUnit === undefined) return undefined;

    return new tr.b.Scalar(aggregatedUnit, aggregatedValue);
  };

  /**
   * @constructor
   */
  function MemoryAllocatorDumpLink(source, target, opt_importance) {
    this.source = source;
    this.target = target;
    this.importance = opt_importance;
    this.size = undefined;
  }

  /**
   * Types of size numeric information.
   *
   * @enum
   */
  const MemoryAllocatorDumpInfoType = {
    // The provided size of a MemoryAllocatorDump was less than the aggregated
    // size of its children.
    //
    // Mandatory extra args:
    //   * providedSize: The inconsistent provided size.
    //   * dependencySize: The aggregated size of the children.
    PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN: 0,

    // The provided size of a MemoryAllocatorDump was less than the size of its
    // largest owner.
    //
    // Mandatory extra args:
    //   * providedSize: The inconsistent provided size.
    //   * dependencySize: The size of the largest owner.
    PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER: 1
  };

  return {
    MemoryAllocatorDump,
    MemoryAllocatorDumpLink,
    MemoryAllocatorDumpInfoType,
  };
});
</script>
